using Godot;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using ZFramework;

namespace PaintAWorld.Gd
{
	public partial class WorldChunkInstance : Node3D
	{
		[Export]
		public PackedScene voxelCollisionPrefab;

		[Export]
		public MeshInstance3D terrainInstance;
		
		private StaticBody3D terrainBody;

		private CollisionShape3D[,,] _voxelCollisions = new CollisionShape3D[WorldChunk.SIZE, WorldChunk.SIZE, WorldChunk.SIZE];
		private List<StaticBody3D> _constructionCollisions = [];

		private Stack<CollisionShape3D> _recycledCollisions = [];

		public void RegenerateTerrain(VoxelModel data)
		{
			Debug.Assert(data != null);

			terrainInstance.Mesh ??= new ArrayMesh();
			var mesh = terrainInstance.Mesh as ArrayMesh;

			mesh.ClearSurfaces();

			var builder = new MeshBuilder();

			Span<int> xOffset = stackalloc int[] { -1, 1, 0, 0, 0, 0 };
			Span<int> yOffset = stackalloc int[] { 0, 0, -1, 1, 0, 0 };
			Span<int> zOffset = stackalloc int[] { 0, 0, 0, 0, -1, 1 };
			Span<Facing> facings = stackalloc Facing[] { Facing.West, Facing.East, Facing.Down, Facing.Up, Facing.North, Facing.South };

			for (var z = 0; z < data.Length; z++)
			{
				for (var y = 0; y < data.Height; y++)
				{
					for (var x = 0; x < data.Width; x++)
					{
						if (!data.HasVoxel(x, y, z))
						{
							if (_voxelCollisions[x, y, z] != null)
							{
								terrainBody.RemoveChild(_voxelCollisions[x, y, z]);
								_recycledCollisions.Push(_voxelCollisions[x, y, z]);
								_voxelCollisions[x, y, z] = null;
							}
							continue;
						}

						var facing = Facing.All;
						for (var i = 0; i < 6; i++)
						{
							if (data.HasVoxel(x + xOffset[i], y + yOffset[i], z + zOffset[i]))
							{
								facing &= ~facings[i];
							}
						}

						if (facing != Facing.None)
						{
							if (_voxelCollisions[x, y, z] == null)
							{
								var collision = GetVoxelCollision();

								collision.Position = new(x + 0.5f, y + 0.5f, z + 0.5f);
								terrainBody.AddChild(collision);
								_voxelCollisions[x, y, z] = collision;
							}
							builder.AddCube(x, y, z, Vector3.Zero, 1.0f, facing, data.ColorAt(x, y, z));
						}
					}
				}
			}

			if (!builder.IsEmpty)
			{
				var arrays = new Godot.Collections.Array();

				builder.Build(ref arrays);


				mesh.AddSurfaceFromArrays(Mesh.PrimitiveType.Triangles, arrays);
			}
		}

		private CollisionShape3D GetVoxelCollision()
		{
			if (_recycledCollisions.Count > 0)
			{
				return _recycledCollisions.Pop();
			}
			return voxelCollisionPrefab.Instantiate<CollisionShape3D>();
		}
	}
}
